// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include <cxxopts.hpp>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/strategy/strategy.h"

using namespace coin2::exchange::base::symbology;
using namespace coin2::base::config;
using namespace coin2::exchange::feed;
using namespace coin2::app;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderUpdate;

class BookMoveStrategy : public ::coin2::strategy::IStrategy {
  std::map<std::string, std::pair<double, double>> last_prc;
  void onBookFeed(const FeedUpdate& upd) override {
    auto& book = upd.book();
    auto bbop = std::make_pair(book.Ask0()->price, book.Bid0()->price);
    if (last_prc[upd.product().relative_norm()] != bbop) {
      last_prc[upd.product().relative_norm()] = bbop;
      std::cout << fmt::format("BOOK {} {} {} {} {} {}",
          Iso8601FromTimestamp(upd.timestamp()),
          MarketType_Name(upd.market()),
          ExchangeType_Name(upd.exchange()),
          upd.product().relative_norm(),
          book.Ask0()->price,
          book.Bid0()->price) << std::endl;
    }
  }
};

class PrintStrategy : public ::coin2::strategy::IStrategy {
  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager*) override {
    LOG(INFO) << "Printer::InjectFeed() is called";
  }
  void onTradeFeed(const FeedUpdate& upd) override {
    auto& trade = upd.trade();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().pre_read_available() << ","
              << upd.ts().pre_read_timestamp() << "," << upd.ts().post_read_available() << ","
              << upd.ts().post_read_timestamp() << "," << upd.ts().exchange_publish_timestamp()
              << "," << Iso8601FromTimestamp(upd.trade().timestamp) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << trade << ")" << std::endl;
  }
  void onLiquidationFeed(const FeedUpdate& upd) override {
    auto& liquidation_order = upd.liquidation_order();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << liquidation_order << ")" << std::endl;
  }
  void onOpenInterestFeed(const FeedUpdate& upd) override {
    auto& open_interest = upd.open_interest();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << open_interest << ")" << std::endl;
  }
  void onMarkPriceFeed(const FeedUpdate& upd) override {
    auto& mark_price = upd.mark_price();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << mark_price << ")" << std::endl;
  }
  void onIndexFeed(const FeedUpdate& upd) override {
    auto& index = upd.index();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << index << ")" << std::endl;
  }
  void onFundingRateFeed(const FeedUpdate& upd) override {
    auto& funding_rate = upd.funding_rate();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << funding_rate << ")" << std::endl;
  }
  void onIvGreeksFeed(const FeedUpdate& upd) override {
    auto& iv_greeks = upd.iv_greeks();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << iv_greeks << ")" << std::endl;
  }
  void onNavFeed(const FeedUpdate& upd) override {
    auto& nav = upd.nav();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << nav << ")" << std::endl;
  }
  void onWalletFeed(const FeedUpdate& upd) override {
    auto& wallet = upd.wallet();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << wallet << ")" << std::endl;
  }
  void onMarketAnalysisFeed(const FeedUpdate& upd) override {
    auto& market_analysis = upd.market_analysis();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << market_analysis << ")" << std::endl;
  }
  void onTopLongShortAccountRatioFeed(const FeedUpdate& upd) override {
    auto& ratio = upd.top_long_short_account_ratio();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm()
              << "(size " << ratio.Size() << ", "
              << "interval " << TradingDataInterval_Name(ratio.Interval()) << ", "
              << *(ratio.Get().value()) << ")" << std::endl;
  }
  void onTopLongShortPositionRatioFeed(const FeedUpdate& upd) override {
    auto& ratio = upd.top_long_short_position_ratio();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm()
              << "(size " << ratio.Size() << ", "
              << "interval " << TradingDataInterval_Name(ratio.Interval()) << ", "
              << *(ratio.Get().value()) << ")" << std::endl;
  }
  void onGlobalLongShortAccountRatioFeed(const FeedUpdate& upd) override {
    auto& ratio = upd.global_long_short_account_ratio();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm()
              << "(size " << ratio.Size() << ", "
              << "interval " << TradingDataInterval_Name(ratio.Interval()) << ", "
              << *(ratio.Get().value()) << ")" << std::endl;
  }
  void onTakerBuySellRatioFeed(const FeedUpdate& upd) override {
    auto& ratio = upd.taker_buy_sell_ratio();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm()
              << "(size " << ratio.Size() << ", "
              << "interval " << TradingDataInterval_Name(ratio.Interval()) << ", "
              << *(ratio.Get().value()) << ")" << std::endl;
  }
  void onOpenInterestHistFeed(const FeedUpdate& upd) override {
    auto& open_interest_hist = upd.open_interest_hist();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm()
              << "(size " << open_interest_hist.Size() << ", "
              << "interval " << TradingDataInterval_Name(open_interest_hist.Interval()) << ", "
              << *(open_interest_hist.Get().value()) << ")" << std::endl;
  }
  void onBookFeed(const FeedUpdate& upd) override {
    auto& book = upd.book();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().pre_read_available() << ","
              << upd.ts().pre_read_timestamp() << "," << upd.ts().post_read_available() << ","
              << upd.ts().post_read_timestamp() << " " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(upd.timestamp()) << " " << std::setw(17)
              << upd.product().relative_norm() << "(" << book << ")" << std::endl;
  }
  void onAccountInfo(const OrderUpdate& upd) override {
    auto& info = upd.account_info();
    nlohmann::json j;
    auto& balances = j["balances"];
    for (const auto& [k, v] : info.GetBalances()) {
      balances[k] = v.total();
    }
    if (upd.mea().market == MarketType::Futures) {
      auto& positions = j["positions"];
      for (const auto& [k, v] : info.GetPositions()) {
        positions[k] = v.net_position();
      }
    }

    std::cout << "[" << std::setw(7) << MarketType_Name(upd.mea().market) << "," << std::setw(8)
              << ExchangeType_Name(upd.mea().exchange) << "] "
              << Iso8601FromTimestamp(upd.timestamp()) << "(" << j.dump(2) << ")" << std::endl;
  }
  void onAccountOrder(const OrderUpdate& upd) override {
    auto& event = upd.product_order_info().event();

    std::cout << "[" << std::setw(7) << MarketType_Name(upd.mea().market) << "," << std::setw(8)
              << ExchangeType_Name(upd.mea().exchange) << "] "
              << upd.ts().exchange_publish_timestamp() << ", "
              << Iso8601FromTimestamp(upd.timestamp()) << "("
              << "onProductOrderInfo: " << coin::proto::OrderEvent_OrderEventType_Name(event.type())
              << ")" << std::endl;
  }

  void onOrderLog(const coin::proto::OrderGatewayLog& log) override {
    // std::cout << "onOrderLog: " << log.DebugString() << std::endl;
  }

  void onKlineFeed(const FeedUpdate& upd) override {
    auto& klines = upd.klines();
    std::cout << "[" << std::setw(7) << MarketType_Name(upd.market()) << "," << std::setw(8)
              << ExchangeType_Name(upd.exchange()) << "] " << upd.ts().exchange_publish_timestamp()
              << ", " << Iso8601FromTimestamp(klines.Get().value()->timestamp) << " "
              << std::setw(17) << upd.product().relative_norm() << "(size " << klines.Size() << ", "
              << *(klines.Get().value()) << ")" << std::endl;
  }
};

int main(int argc, char* argv[]) {
  google::InitGoogleLogging(argv[0]);

  DriverConfig app;

  cxxopts::Options opt("Printer", "Print feed and order events.");
  opt.add_options()(
      "book_move",
      "book_move mode",
      cxxopts::value<int32_t>()->default_value("0"));
  AddDriverOptions(&opt);
  AddIntervalOptions(&opt);
  AddProductOptions(&opt);

  auto res = opt.parse(argc, argv);
  ParseDriverOptions(res, &app);
  ParseIntervalOptions(
      res,
      app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval());
  ParseProductOptions(res, app.mutable_feed());

  LOG(INFO) << app.DebugString();

  if (res["book_move"].as<int>()) {
    BookMoveStrategy book_move;
    Driver driver(app, &book_move);
    driver.Run();
  } else {
    PrintStrategy printer;  // printer unused if feed-writer
    Driver driver(app, &printer);
    driver.Run();
  }
  return 0;
}
